//----------------------------------------------------------------------------
// M A R I T I M E  R E S E A R C H  I N S T I T U T E  N E T H E R L A N D S
//----------------------------------------------------------------------------
// Copyright (C) 2008 - MARIN - All rights reserved - http://www.marin.nl
//----------------------------------------------------------------------------
// Program    : mermaid
// Module     : vbm
// File       : Array2D.h
//----------------------------------------------------------------------------

#ifndef ARRAY2D_H
#define ARRAY2D_H

#include <string.h> // memcpy
#include "assertplus.h"

#define REAL double

template <class T> class Array2DcolumnIterator;
template <class T> class Array2DrowIterator;

/// two dimensional array class with efficient memory use
template <class T>
class Array2D
{
#ifdef UNITTEST
    FRIEND_TEST(Array2D, default_constructor);
    FRIEND_TEST(Array2D, size_constructor);
    FRIEND_TEST(Array2D, copy);
#endif // UNITTEST

    friend class Array2DcolumnIterator<T>;
    friend class Array2DrowIterator<T>;

public:

    /// default constructor
    Array2D()
        : m_Dim1(0)
        , m_Dim2(0)
        , m_Pitch(0)
        , m_pElements(0)
        , m_pPointers(0)
    {
    }

    /// constructor to create array of specified size
    Array2D(unsigned dim1, unsigned dim2)
        : m_Dim1(dim1)
        , m_Dim2(dim2)
        , m_Pitch(0)
        , m_pElements(0)
        , m_pPointers(0)
    {
        m_pPointers = new T*[dim1];
        m_pElements = new T[dim1 * dim2];
        for (unsigned r = 0; r < dim1; ++r)
        {
            m_pPointers[r] = m_pElements + (r * dim2);
        }
    }

    /// copy constructor
    Array2D(const Array2D& a)
        : m_Dim1(a.dim1())
        , m_Dim2(a.dim2())
        , m_Pitch(0)
        , m_pElements(0)
        , m_pPointers(0)
    {
        copy(a);
    }

    /// destructor
    virtual ~Array2D()
    {
        delete [] m_pPointers;
        delete [] m_pElements;
    }

    /// assignment operator
    Array2D & operator=(const Array2D& a)
    {
        if (&a != this)
        {
            copy(a);
        }
        return *this;
    }

    /// assigns value to all elements
    Array2D & operator=(T value)
    {
        // expensive!
        int n = dim1() * dim2();
        for (int i = 0; i < n; ++i)
        {
            m_pElements[i] = value;
        }
        return *this;
    }

    /// addition    
    Array2D & operator+=(T value)
    {
        // expensive!
        int n = dim1() * dim2();
        for (int i = 0; i < n; ++i)
        {
            m_pElements[i] += value;
        }
        return *this;
    }

    /// multiplication    
    Array2D & operator*=(T value)
    {
        // expensive!
        int n = dim1() * dim2();
        for (int i = 0; i < n; ++i)
        {
            m_pElements[i] *= value;
        }
        return *this;
    }

    /// equality operator
    bool operator==(const Array2D& a) const
    {
        if (dim1() != a.dim1() || dim2() != a.dim2())
        {
            return false;
        }
        // expensive!
        return !memcmp(m_pElements, a.m_pElements, dim1() * dim2() * sizeof (T));
    }

    /// inequality operator
    inline bool operator!=(const Array2D& a) const
    {
        return !(*this == a);
    }

    /// first index operator
    inline T * operator[](unsigned y)
    {
        ASSERT(y < dim1(), "y=" << y << ", dim1()=" << dim1());
        return m_pPointers[y];
    }

    /// first index operator (read-only)
    inline const T * operator[](unsigned y)const
    {
        ASSERT(y < dim1(), "y=" << y << ", dim1()=" << dim1());
        return m_pPointers[y];
    }

    /// access of element x,y
    inline T & operator()(unsigned x, unsigned y)
    {
        ASSERT(x < dim1(), "x=" << x << ", dim1()=" << dim1());
        ASSERT(y < dim2(), "y=" << y << ", dim2()=" << dim2());
        return m_pElements[dim2() * x + y];
    }

    /// access of element x,y (read-only)
    inline const T & operator()(unsigned x, unsigned y)const
    {
        ASSERT(x < dim1(), "x=" << x << ", dim1()=" << dim1());
        ASSERT(y < dim2(), "y=" << y << ", dim2()=" << dim2());
        return m_pElements[dim2() * x + y];
    }

    /// returns number of rows
    inline unsigned dim1() const
    {
        return m_Dim1;
    }

    /// returns number or columns
    inline unsigned dim2() const
    {
        return m_Dim2;
    }
    
    /// returns number of rows
    inline unsigned rows() const
    {
        return dim1();
    }

    /// returns number or columns
    inline unsigned columns() const
    {
        return dim2();
    }
    
    /// clears array (all elements zero)
    inline void clear()
    {
        *this = 0;
    }

    /// resizes array to specified size (previous contents are lost)
    void resize(unsigned dim1, unsigned dim2)
    {
        delete [] m_pPointers;
        delete [] m_pElements;
        m_Dim1 = dim1;
        m_Dim2 = dim2;
        m_pPointers = new T*[dim1];
        m_pElements = new T[dim1 * dim2];
        for (unsigned r = 0; r < dim1; ++r)
        {
            m_pPointers[r] = m_pElements + (r * dim2);
        }
    }

    /// returns address
    T* getElements() const
    {
        return m_pElements;
    }

private:

    /// copies contents of array
    void copy(const Array2D &m)
    {
        resize(m.dim1(), m.dim2());
        memcpy(m_pElements, m.m_pElements, dim1() * dim2() * sizeof (T));
        //        for (unsigned i = 0; i < dim1(); ++i)
        //        {
        //            for (unsigned j = 0; j < dim2(); ++j)
        //            {
        //                (*this)[i][j] = m[i][j];
        //            }
        //        }
    }

    // data members
    unsigned m_Dim1; ///< number of rows
    unsigned m_Dim2; ///< number of columns
    unsigned m_Pitch; ///< pitch for addressing subarrays
    T *m_pElements; ///< pointer to array of elements
    T **m_pPointers; ///< pointers to dim1 of elements
};

/// partial specializations of clear function

//template <> void Array2D<bool>::clear()
//{
//    *this = false;
//}

//template <> void Array2D<std::string>::clear()
//{
//    *this = std::string("");
//}


/// todo: not yet implemented correctly!!!

template <class T> class Array2DrowIterator;

template <class T> bool operator==(const Array2DrowIterator<T>& a, const Array2DrowIterator<T>& b);
template <class T> bool operator!=(const Array2DrowIterator<T>& a, const Array2DrowIterator<T>& b);

template <class T> 
class Array2DrowIterator
{

    friend bool operator==<T>(const Array2DrowIterator<T>& a, const Array2DrowIterator<T>& b);
    friend bool operator!=<T>(const Array2DrowIterator<T>& a, const Array2DrowIterator<T>& b);

public:

    /// constructor
    Array2DrowIterator<T>(Array2D<T>& ar, int index) : m_array(ar), m_index(index)
    {
    }
    
    /// dereference operator
    T & operator*() 
    { 
        return m_array[m_index]; 
    }
    
    /// prefix increment oprator
    Array2DrowIterator<T>& operator++() 
    { 
        ++m_index; 
        return *this; 
    }
    
    /// postfix increment operator
    Array2DrowIterator<T> operator++(int)
    {
        Array2DrowIterator<T> clone(*this);
        ++m_index;
        return clone;
    }
    
  private:
    
    // data members
    Array2D<T>& m_array; /// array to work on
    int m_index; /// index of current element
};

/// equality operator
template <class T> bool operator==(const Array2DrowIterator<T>& a, const Array2DrowIterator<T>& b)
{
    return a.m_array == b.m_array && a.m_index == b.m_index;
}

/// inequality operator
template <class T> bool operator!=(const Array2DrowIterator<T>& a, const Array2DrowIterator<T>& b)
{
    return !operator==(a, b);
}

#endif // ARRAY2D_H
